Entity Data Model .edmxから自動的に生成されたPOCOクラスをシリアル化してみました。
JsonConvert.SerializeObject
次のエラーが発生しました:
エラータイプSystem.data.entityで自己参照ループが検出されました。
この問題を解決するにはどうすればよいですか?
async
メソッド呼び出し(a Task
)の結果をシリアル化し、await
ステートメントの前に付けるのを忘れたときに発生しました。
Entity Data Model .edmxから自動的に生成されたPOCOクラスをシリアル化してみました。
JsonConvert.SerializeObject
次のエラーが発生しました:
エラータイプSystem.data.entityで自己参照ループが検出されました。
この問題を解決するにはどうすればよいですか?
async
メソッド呼び出し(a Task
)の結果をシリアル化し、await
ステートメントの前に付けるのを忘れたときに発生しました。
回答:
それが最高のソリューションでした https://code.msdn.microsoft.com/Loop-Reference-handling-in-caaffaf7
(私はこれを選びました/他の多くの人と同じように試しました)
json.netシリアライザーには、循環参照を無視するオプションがあります。次のコードをWebApiConfig.cs
ファイルに入れます:
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Ignore;
単純な修正により、シリアライザはループを引き起こす参照を無視します。ただし、制限があります。
この修正を非API ASP.NETプロジェクトで使用する場合は、上記の行をに追加できますがGlobal.asax.cs
、最初に次の行を追加します。
var config = GlobalConfiguration.Configuration;
これを.Net Coreプロジェクトで使用する場合は、次のように変更できますStartup.cs
。
var mvc = services.AddMvc(options =>
{
...
})
.AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
この2番目の修正は最初の修正と似ています。コードを次のように変更するだけです。
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling
= Newtonsoft.Json.ReferenceLoopHandling.Serialize;
config.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling
= Newtonsoft.Json.PreserveReferencesHandling.Objects;
この設定を適用すると、データの形状が変更されます。
[
{
"$id":"1",
"Category":{
"$id":"2",
"Products":[
{
"$id":"3",
"Category":{
"$ref":"2"
},
"Id":2,
"Name":"Yogurt"
},
{
"$ref":"1"
}
],
"Id":1,
"Name":"Diary"
},
"Id":1,
"Name":"Whole Milk"
},
{
"$ref":"3"
}
]
$ idと$ refはすべての参照を保持し、オブジェクトグラフレベルをフラットにしますが、クライアントコードはデータを使用するために形状の変更を知る必要があり、JSON.NETシリアライザーにも適用されます。
この修正は、モデルまたはプロパティレベルでのシリアル化動作を制御するためにモデルクラスの属性を装飾することです。プロパティを無視するには:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
[IgnoreDataMember]
public virtual ICollection<Product> Products { get; set; }
}
JsonIgnoreはJSON.NET用で、IgnoreDataMemberはXmlDCSerializer用です。参照を保持するには:
// Fix 3
[JsonObject(IsReference = true)]
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
// Fix 3
//[JsonIgnore]
//[IgnoreDataMember]
public virtual ICollection<Product> Products { get; set; }
}
[DataContract(IsReference = true)]
public class Product
{
[Key]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public virtual Category Category { get; set; }
}
JsonObject(IsReference = true)]
JSON.NET用で[DataContract(IsReference = true)]
あり、XmlDCSerializer用です。注意:DataContract
クラスに適用した後DataMember
、シリアル化するプロパティに追加する必要があります。
属性はjsonとxmlの両方のシリアライザーに適用でき、モデルクラスをより詳細に制御できます。
[JsonIgnore]
上記の属性を使用するとうまくいきました。
ReferenceLoopHandling.Error
(デフォルト)参照ループに遭遇するとエラーになります。これが、例外が発生する理由です。ReferenceLoopHandling.Serialize
オブジェクトが入れ子になっているが無期限ではない場合に便利です。ReferenceLoopHandling.Ignore
オブジェクトがそれ自体の子オブジェクトである場合、オブジェクトはシリアル化されません。 例:
JsonConvert.SerializeObject(YourPOCOHere, Formatting.Indented,
new JsonSerializerSettings {
ReferenceLoopHandling = ReferenceLoopHandling.Serialize
});
無期限にネストされているオブジェクトをシリアル化する必要がある場合は、PreserveObjectReferencesを使用してStackOverflowExceptionを回避できます。
例:
JsonConvert.SerializeObject(YourPOCOHere, Formatting.Indented,
new JsonSerializerSettings {
PreserveReferencesHandling = PreserveReferencesHandling.Objects
});
シリアル化するオブジェクトにとって意味のあるものを選びます。
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
はそれが機能するために使用しました
ReferenceLoopHandling.Serialize
すると、シリアライザが無限再帰ループに入り、スタックがオーバーフローします。
修正は、ループ参照を無視し、それらをシリアル化しないことです。この動作はで指定されていJsonSerializerSettings
ます。
JsonConvert
過負荷のあるシングル:
JsonConvert.SerializeObject(YourObject, Formatting.Indented,
new JsonSerializerSettings() {
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
}
);
Application_Start()
Global.asax.csのコードを使用したグローバル設定:
JsonConvert.DefaultSettings = () => new JsonSerializerSettings {
Formatting = Newtonsoft.Json.Formatting.Indented,
ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
};
リファレンス:https : //github.com/JamesNK/Newtonsoft.Json/issues/78
これを行う最も簡単な方法は、nuget
からJson.NET をインストール[JsonIgnore]
し、クラスの仮想プロパティに属性を追加することです。次に例を示します。
public string Name { get; set; }
public string Description { get; set; }
public Nullable<int> Project_ID { get; set; }
[JsonIgnore]
public virtual Project Project { get; set; }
最近では、通過させたいプロパティのみを使用してモデルを作成しているため、軽量で、不要なコレクションが含まれておらず、生成されたファイルを再構築しても変更が失われません...
.NET Core 1.0では、これをStartup.csファイルのグローバル設定として設定できます。
using System.Buffers;
using Microsoft.AspNetCore.Mvc.Formatters;
using Newtonsoft.Json;
// beginning of Startup class
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.OutputFormatters.Clear();
options.OutputFormatters.Add(new JsonOutputFormatter(new JsonSerializerSettings(){
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
}, ArrayPool<char>.Shared));
});
}
.NET Core 2.xを使用している場合は、Startup.csのConfigureServicesセクションを更新します。
https://docs.microsoft.com/en-us/ef/core/querying/related-data#related-data-and-serialization
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMvc()
.AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
...
}
MVCなしで.NET Core 3.xを使用している場合は、次のようになります。
services.AddControllers()
.AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
Entity Frameworkとデータベースファーストの設計パターンを使用している場合、この参照ループの処理はほぼ必須です。
services.AddMvc()
か?
ループの問題があるときにNEWTONSOFTJSONを使用してシリアル化するには、私の場合、global.asaxまたはapiconfigを変更する必要はありませんでした。ループ処理を無視してJsonSerializesSettingsを使用するだけです。
JsonSerializerSettings jss = new JsonSerializerSettings();
jss.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var lst = db.shCards.Where(m => m.CardID == id).ToList();
string json = JsonConvert.SerializeObject(lst, jss);
Newtonsoft.Json.JsonConvert.SerializeObject(objToSerialize, new Newtonsoft.Json.JsonSerializerSettings() {ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore});
次のように、これらの2行をDbContextクラスコンストラクターに追加して、自己参照ループを無効にすることができます。
public TestContext()
: base("name=TestContext")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
プロパティに属性を適用することもできます。の[JsonProperty( ReferenceLoopHandling = ... )]
属性はこれに適しています。
例えば:
/// <summary>
/// Represents the exception information of an event
/// </summary>
public class ExceptionInfo
{
// ...code omitted for brevity...
/// <summary>
/// An inner (nested) error.
/// </summary>
[JsonProperty( ReferenceLoopHandling = ReferenceLoopHandling.Ignore, IsReference = true )]
public ExceptionInfo Inner { get; set; }
// ...code omitted for brevity...
}
Jaansさんのお役に立てば幸いです
ループ参照を無視し、MVC 6でグローバルにシリアル化しないようにするには、startup.csで以下を使用します。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().Configure<MvcOptions>(options =>
{
options.OutputFormatters.RemoveTypesOf<JsonOutputFormatter>();
var jsonOutputFormatter = new JsonOutputFormatter();
jsonOutputFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
options.OutputFormatters.Insert(0, jsonOutputFormatter);
});
}
私にとっては別のルートに行かなければなりませんでした。JSON.Netシリアライザーを修正しようとする代わりに、データコンテキストで遅延読み込みを実行する必要がありました。
これをベースリポジトリに追加しました:
context.Configuration.ProxyCreationEnabled = false;
「コンテキスト」オブジェクトは、依存関係の注入を使用するため、ベースリポジトリで使用するコンストラクタパラメータです。代わりに、データコンテキストをインスタンス化する任意の場所でProxyCreationEnabledプロパティを変更できます。
http://techie-tid-bits.blogspot.com/2015/09/jsonnet-serializer-and-error-self.html
私にはこの例外があり、私の解決策は簡単でシンプルです。
JsonIgnore属性を追加してReferencedプロパティを無視します。
[JsonIgnore]
public MyClass currentClass { get; set; }
逆シリアル化するときにプロパティをリセットします。
Source = JsonConvert.DeserializeObject<MyObject>(JsonTxt);
foreach (var item in Source)
{
Source.MyClass = item;
}
Newtonsoft.Jsonを使用します。
[JsonIgnore]
チーム:
これはASP.NET Coreで動作します。上記の課題は、「設定を無視するように設定する」方法です。アプリケーションの設定方法によっては、非常に困難な場合があります。ここに私のために働いたものがあります。
これは、public void ConfigureServices(IServiceCollection services)セクションに配置できます。
services.AddMvc().AddJsonOptions(opt =>
{
opt.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
人々はすでに[JsonIgnore]がクラスの仮想プロパティに追加されることについて話しました、例えば:
[JsonIgnore]
public virtual Project Project { get; set; }
また、別のオプション[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]も共有します。これは、プロパティがnullの場合にのみシリアル化から除外します。
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public virtual Project Project { get; set; }
.NET Core 3.0の場合は、次に示すように、Startup.csクラスを更新します。
public void ConfigureServices(IServiceCollection services)
{
...
services.AddControllers()
.AddNewtonsoftJson(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
...
}
参照:https : //devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-core-3-0-preview-5/
カスタム構成JsonSerializerSettingsで解決した私の問題:
services.AddMvc(
// ...
).AddJsonOptions(opt =>
{
opt.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Serialize;
opt.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.Objects;
});
私は同じ問題に直面していて、JsonSettingを使用して自己参照エラーを無視しようとしましたが、自己参照が非常に深く、ドットネットプロセスがJsonの書き込み値でハングアップするクラスが見つかるまでは、この作業はうまくいきました。
私の問題
public partial class Company : BaseModel
{
public Company()
{
CompanyUsers = new HashSet<CompanyUser>();
}
public string Name { get; set; }
public virtual ICollection<CompanyUser> CompanyUsers { get; set; }
}
public partial class CompanyUser
{
public int Id { get; set; }
public int CompanyId { get; set; }
public int UserId { get; set; }
public virtual Company Company { get; set; }
public virtual User User { get; set; }
}
public partial class User : BaseModel
{
public User()
{
CompanyUsers = new HashSet<CompanyUser>();
}
public string DisplayName { get; set; }
public virtual ICollection<CompanyUser> CompanyUsers { get; set; }
}
CompanyUserを参照しているUserクラスで問題を確認できます自己クラスを。
ここで、すべてのリレーショナルプロパティを含むGetAllメソッドを呼び出します。
cs.GetAll("CompanyUsers", "CompanyUsers.User");
この段階では私のDotNetCoreプロセスのハング、するJsonResultを実行する値を書き込む...とは来ません。私のStartup.csでは、JsonOptionをすでに設定しています。なんらかの理由でEFCoreにネストされたプロパティが含まれているため、Efに指定するように求めていません。
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
予想される動作はこれでなければなりません
EfCoreさん、「CompanyUsers」データもCompanyクラスに含めて、データに簡単にアクセスできるようにしてください。
その後
EfCoreさん、「CompanyUsers.User」データも含めて、このCompany.CompanyUsers.First()。User.DisplayNameのようなデータに簡単にアクセスできるようにしてください 。
この段階では、この"Company.CompanyUsers.First()。User.DisplayName"のみを取得し、自己参照の問題の原因となるCompany.CompanyUsers.First()。User.CompanyUsersを取得しないようにする必要があります。厳密には、CompanyUsersはナビゲーションプロパティであるため、User.CompanyUsersを取得するべきではありません。しかし、EfCoreはとても興奮し、User.CompanyUsersをくれます。
そのため、オブジェクトから除外されるプロパティの拡張メソッドを作成することにしました(実際には、プロパティをnullに設定するだけで除外されるわけではありません)。それだけでなく、配列のプロパティでも機能します。以下は、他のユーザーのためにnugetパッケージもエクスポートするコードです(これが誰かに役立つかどうかはわかりません)。理由は単純です。私は.Select(n => new {n.p1、n.p2});を書くのが面倒だからです。1つのプロパティのみを除外するselectステートメントを記述したくないだけです。
私は急いで書いたので、これは最良のコードではありません(ある段階で更新します)。これは、配列を持つオブジェクトから除外(nullに設定)したい人にも役立つかもしれません。
public static class PropertyExtensions
{
public static void Exclude<T>(this T obj, Expression<Func<T, object>> expression)
{
var visitor = new PropertyVisitor<T>();
visitor.Visit(expression.Body);
visitor.Path.Reverse();
List<MemberInfo> paths = visitor.Path;
Action<List<MemberInfo>, object> act = null;
int recursiveLevel = 0;
act = (List<MemberInfo> vPath, object vObj) =>
{
// set last propert to null thats what we want to avoid the self-referencing error.
if (recursiveLevel == vPath.Count - 1)
{
if (vObj == null) throw new ArgumentNullException("Object cannot be null");
vObj.GetType().GetMethod($"set_{vPath.ElementAt(recursiveLevel).Name}").Invoke(vObj, new object[] { null });
return;
}
var pi = vObj.GetType().GetProperty(vPath.ElementAt(recursiveLevel).Name);
if (pi == null) return;
var pv = pi.GetValue(vObj, null);
if (pi.PropertyType.IsArray || pi.PropertyType.Name.Contains("HashSet`1") || pi.PropertyType.Name.Contains("ICollection`1"))
{
var ele = (IEnumerator)pv.GetType().GetMethod("GetEnumerator").Invoke(pv, null);
while (ele.MoveNext())
{
recursiveLevel++;
var arrItem = ele.Current;
act(vPath, arrItem);
recursiveLevel--;
}
if (recursiveLevel != 0) recursiveLevel--;
return;
}
else
{
recursiveLevel++;
act(vPath, pv);
}
if (recursiveLevel != 0) recursiveLevel--;
};
// check if the root level propert is array
if (obj.GetType().IsArray)
{
var ele = (IEnumerator)obj.GetType().GetMethod("GetEnumerator").Invoke(obj, null);
while (ele.MoveNext())
{
recursiveLevel = 0;
var arrItem = ele.Current;
act(paths, arrItem);
}
}
else
{
recursiveLevel = 0;
act(paths, obj);
}
}
public static T Explode<T>(this T[] obj)
{
return obj.FirstOrDefault();
}
public static T Explode<T>(this ICollection<T> obj)
{
return obj.FirstOrDefault();
}
}
上記の拡張クラスを使用すると、プロパティをnullに設定して、配列であっても自己参照ループを回避できます。
式ビルダー
internal class PropertyVisitor<T> : ExpressionVisitor
{
public readonly List<MemberInfo> Path = new List<MemberInfo>();
public Expression Modify(Expression expression)
{
return Visit(expression);
}
protected override Expression VisitMember(MemberExpression node)
{
if (!(node.Member is PropertyInfo))
{
throw new ArgumentException("The path can only contain properties", nameof(node));
}
Path.Add(node.Member);
return base.VisitMember(node);
}
}
使用法:
モデルクラス
public class Person
{
public string Name { get; set; }
public Address AddressDetail { get; set; }
}
public class Address
{
public string Street { get; set; }
public Country CountryDetail { get; set; }
public Country[] CountryDetail2 { get; set; }
}
public class Country
{
public string CountryName { get; set; }
public Person[] CountryDetail { get; set; }
}
ダミーデータ
var p = new Person
{
Name = "Adeel Rizvi",
AddressDetail = new Address
{
Street = "Sydney",
CountryDetail = new Country
{
CountryName = "AU"
}
}
};
var p1 = new Person
{
Name = "Adeel Rizvi",
AddressDetail = new Address
{
Street = "Sydney",
CountryDetail2 = new Country[]
{
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A1" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A2" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A3" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A4" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A5" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A6" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A7" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A8" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A9" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A1" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A2" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A3" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A4" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A5" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A6" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A7" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A8" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
new Country{ CountryName = "AU", CountryDetail = new Person[]{ new Person { Name = "A9" }, new Person { Name = "A1" }, new Person { Name = "A1" } } },
}
}
};
ケース:
ケース1:配列なしでプロパティのみを除外する
p.Exclude(n => n.AddressDetail.CountryDetail.CountryName);
ケース2:1つの配列でプロパティを除外する
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryName);
ケース3:2つのネストされた配列を持つプロパティを除外する
p1.Exclude(n => n.AddressDetail.CountryDetail2.Explode().CountryDetail.Explode().Name);
ケース4:Includeを含むEF GetAllクエリ
var query = cs.GetAll("CompanyUsers", "CompanyUsers.User").ToArray();
query.Exclude(n => n.Explode().CompanyUsers.Explode().User.CompanyUsers);
return query;
Explode()メソッドは、式ビルダーが配列プロパティからプロパティを取得するための拡張メソッドでもあることに気付きました。配列プロパティがある場合は常に、.Explode()。YourPropertyToExcludeまたは.Explode()。Property1.MyArrayProperty.Explode()。MyStupidPropertyを使用します。上記のコードは、私が望むほど深く自己参照を回避するのに役立ちます。GetAllを使用して、不要なプロパティを除外できるようになりました。
この大きな投稿を読んでくれてありがとう!
ループしないので、これは私のために働きました-
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ここですべて解決しました-.Net Core 2 WebAPIによるEntity Frameworkの子のシリアライゼーション https://gist.github.com/Kaidanov/f9ad0d79238494432f32b8407942c606
備考をいただければ幸いです。多分誰かがそれをいつか使うことができます。
C#コード:
var jsonSerializerSettings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
};
var jsonString = JsonConvert.SerializeObject(object2Serialize, jsonSerializerSettings);
var filePath = @"E:\json.json";
File.WriteAllText(filePath, jsonString);