简体   繁体   English

使用所需的外键在 OData 中插入实体

[英]Inserting Entities in OData with required Foreign Keys

EDIT-2: After hours of research and almost every odata related link on google turning purple, I found out that the concept of 'deep-inserts' ( link ) exists in the OData specification. EDIT-2:经过数小时的研究,谷歌上几乎所有与 odata 相关的链接都变成了紫色,我发现 OData 规范中存在“深度插入”( 链接)的概念。 So after all, what I'm doing should work, even without the links.所以毕竟,即使没有链接,我所做的也应该有效。 Does anyone know how to enable this on the Microsoft OData client?有谁知道如何在 Microsoft OData 客户端上启用它? Are there any other OData clients out there that support that concept?是否有其他支持该概念的 OData 客户端?

EDIT: Maybe this is the wrong approach, so please tell me if I'm doing it totally wrong.编辑:也许这是错误的方法,所以如果我做的完全错了,请告诉我。 Not being able to save is really blocking our progress!不能保存真的是阻碍了我们的进步!

I have an issue with OData v3.我对 OData v3 有疑问。 I have a class Associate that has a required Address .我有一个 class Associate ,它有一个必需的Address When I try to POST a new Associate, it fails due to the Address property being null (EF6 throws DbUpdateException with foreign key violation).当我尝试发布新的 Associate 时,由于Address属性为 null 而失败(EF6 抛出外键违规的 DbUpdateException)。 My Associate class looks like this:我的Associate class 看起来像这样:

public class Associate
{
    public int Id { get; set; }

    [Required, StringLength(100)]
    public string Name { get; set; }

    [Required, StringLength(50)]
    public string Role { get; set; }

    public bool IsMailReceiver { get; set; }
    public bool IsLegalRepresentative { get; set; }

    [ForeignKey("AddressId")]
    public virtual Address Address { get; set; }
    public int AddressId { get; set; }
}

I use the Microsoft OData client, and try to add the associate in the following way:我使用 Microsoft OData 客户端,并尝试通过以下方式添加关联:

var associate = new Associate { /* ... */ };
context.AddObject("Associates", associate);
context.AddObject("Addresses", associate.Address);

/* UI fills associate data */

context.SetLink(associate, "Address", associate.Address);
context.UpdateObject(associate);
context.UpdateObject(associate.Address);

/* at this point the associate has the address set! */

context.SaveChanges(); // << Exception

On the server, in the controller, the Associate arrives without the foreign key, however.然而,在服务器上,在 controller 中,Associate 到达时没有外键。 When I inspect the POST request with Fiddler, I see why:当我使用 Fiddler 检查 POST 请求时,我明白了原因:

{
    "odata.type" : "xxx.Data.Entities.Associate",
    "AddressId" : 0,
    "Id" : 0,
    "IsLegalRepresentative" : false,
    "IsMailReceiver" : false,
    "Name" : "John Doe",
    "Role" : "Father"
}

The address is not transmitted, even though the generated class on the client has an Address property.地址不会传输,即使在客户端生成的 class 具有Address属性。

How can i solve this problem?我怎么解决这个问题?

I too could not find any information about this - it really feels like an issue in OData. 我也找不到任何关于此的信息 - 这在OData中确实是一个问题。 Here is how I managed to get it to work. 以下是我设法让它发挥作用的方式。

Define the foreign key explicitly 明确定义外键

class Student {
        public int TeacherId { get; set; }

        [Required, ForeignKey("TeacherId")]
        public virtual Teacher Teacher { get; set; }
}

When performing the insert, fetch the related record and fix the model state: 执行插入时,获取相关记录并修复模型状态:

  public IHttpActionResult Post(Student student)
  {
        student.Teacher = this.db.Teacher.FirstOrDefault(i => i.TeacherId == student.TeacherId);
        if (student.Teacher != null)
        {
            this.ModelState.Remove("student.Teacher");
        }

        if (!this.ModelState.IsValid)
        {
            return this.BadRequest(this.ModelState);
        }
  }

So from then on to post a Student, you ignore the Teacher field and just post with TeacherId. 因此,从那时起发布学生,您将忽略教师字段,并使用TeacherId发布。

I haven't tested this with the OData client, but I can't think of why this wouldn't work. 我没有用OData客户端对此进行测试,但我想不出为什么这不起作用。 You will just have to use the Id field rather than the object. 您只需使用Id字段而不是对象。

Basically when you create the object 基本上在创建对象时

var associate = new Associate { /* ... */ };

It is not inserted into the database. 它未插入数据库。 It is created in the memory. 它是在内存中创建的。 When you call 你打电话的时候

context.SaveChanges();

It will be saved in the database. 它将保存在数据库中。 At this point database validation happens and key's are generated. 此时,数据库验证发生并生成密钥。 Assuming your Id is unique identifier, that is generated in the datebase, note that in order for it to get updated value back from the database you need to have StoreGeneratedPattern set to Identity from Entity model view. 假设您的Id是唯一标识符,它是在数据库中生成的,请注意,为了使数据库从数据库中获取更新值,您需要将StoreGeneratedPattern设置为Entity模型视图中的Identity

If this is not done your local context and database context no longer match. 如果不这样做,则本地上下文和数据库上下文不再匹配。 If you where to use that object with reference to something else it would fail. 如果您在何处使用该对象而引用其他内容则会失败。

I assume something like this would work: 我认为这样的事情会起作用:

Address address = new Address{ City = "Tallinn" /*etc*/};
context.SaveChanges();
//At this point Address will be in database context and has Id 
associate = new Associate {  
  name = "Margus",
  role = "Admin",
  receiver = true,
  representative = true,
  AddressId = address.id 
};
context.SaveChanges();

There is no solution to this. 没有解决方案。 I will roll my own context with a notion of change sets that works with web-api. 我将使用与web-api一起使用的变更集概念来推广我自己的上下文。 I will put it on github, when I'm done. 当我完成时,我会把它放在github上。

addlink和setlink工作的唯一方法是,如果外键可以为空,你可以创建一个postput函数调用create link,请看这里

I came across this and I can confirm that it is indeed a problem with the OData client (proxy), although I haven't found any references about it.我遇到了这个,我可以确认这确实是 OData 客户端(代理)的问题,尽管我还没有找到任何关于它的参考资料。 I managed to fix it using a workaround, which is not 100% perfect but works for me.我设法使用变通方法修复了它,该变通方法不是 100% 完美但对我有用。 Here is the code and I will explain about its shortcomings.这是代码,我将解释它的缺点。

public static class DataServiceContextExtensions
{
    public static int PostChanges(this DataServiceContext context)
    {
        using (var client = new WebClient())
        {
            client.Credentials = context.Credentials;
            client.Headers[HttpRequestHeader.ContentType] = "application/json";

            var entities = context.Entities.Where(x => x.State == EntityStates.Added);

            foreach (var descriptor in entities)
            {
                var url = $"{context.BaseUri}{descriptor.Entity.GetType().Name}";
                var data = JsonSerializer.Serialize(descriptor.Entity);

                var response = client.UploadString(url, data);

                context.ChangeState(descriptor.Entity, EntityStates.Detached);
            }

            return entities.Count();
        }
    }
}

As you can see, I am using an extension method over DataServiceContext where I iterate through all the entities stored in the change tracker which are marked as added, and then I use a WebClient to POST a JSON serialized version of them to the OData endpoint, using any credentials that the proxy might have.如您所见,我在DataServiceContext上使用扩展方法,在其中迭代存储在更改跟踪器中标记为已添加的所有实体,然后我使用WebClient将它们的 JSON 序列化版本发布到 OData 端点,使用代理可能拥有的任何凭据。

Problems:问题:

  • First, it only deals with added entities, I'm OK with that, but others may need a more comprehensive solution.首先,它只处理添加的实体,我对此没有意见,但其他人可能需要更全面的解决方案。 The only way I see is to replace WebClient with some other client that can do arbitrary HTTP verbs.我看到的唯一方法是将 WebClient 替换为其他可以执行任意 HTTP 动词的客户端。

  • Second, it doesn't hydrate the entities with the generated primary key.其次,它不会使用生成的主键来混合实体。 Again, I don't need it in my case, but this one will be difficult to solve, as OData does not seem to return it on the result of UploadString.同样,在我的情况下我不需要它,但是这个问题很难解决,因为 OData 似乎不会在 UploadString 的结果上返回它。

  • Third, all URLs are always, by convention, assumed to be composed as BaseUri + entity type (eg, https://foo.bar/MyEntity ).第三,按照惯例,所有 URL 总是假定由 BaseUri + 实体类型组成(例如, https://foo.bar/MyEntity )。 This may or may not always be the case, not 100% sure, again, works in my case.情况可能总是这样,也可能不总是这样,不是 100% 肯定,同样,在我的情况下有效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM